/* * Sun Public License Notice * * The contents of this file are subject to the Sun Public License * Version 1.0 (the "License"). You may not use this file except in * compliance with the License. A copy of the License is available at * http://www.sun.com/ * * The Original Code is Forte for Java, Community Edition. The Initial * Developer of the Original Code is Sun Microsystems, Inc. Portions * Copyright 1997-2000 Sun Microsystems, Inc. All Rights Reserved. */ package org.netbeans.core.output; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Event; import java.awt.Dimension; import java.awt.event.*; import java.awt.datatransfer.StringSelection; import java.io.IOException; import java.io.Writer; import java.util.*; import java.text.MessageFormat; import javax.swing.*; import javax.swing.event.*; import org.openide.TopManager; import org.openide.filesystems.FileObject; import org.openide.loaders.DataObject; import org.openide.loaders.DataObjectNotFoundException; import org.openide.cookies.EditorCookie; import org.openide.text.Line; import org.openide.windows.*; import org.openide.actions.CopyAction; import org.netbeans.core.actions.NextOutJumpAction; import org.netbeans.core.actions.PreviousOutJumpAction; import org.openide.util.actions.ActionPerformer; import org.openide.util.actions.SystemAction; import org.openide.awt.JPopupMenuPlus; /** This class represents one of output panes in one OutputTab (It can be * either stdout either errout). It extends JComponent. Inside this component * is inserted org.openide.text.view.ViewManager. * * @author Petr Hamernik, Jaroslav Tulach */ public final class OutPane extends JList implements MouseListener, ListSelectionListener, KeyListener, ActionPerformer, FocusListener, ActionListener { /** generated Serialized Version UID */ static final long serialVersionUID = -633812069000420549L; /** Information channel for this pane */ OutputWriterImpl writer; /** model for the list */ private PaneWriter model; /** My parent output tab */ OutputTab tab; /** Boolean flag - First jump line was set or not */ boolean jumpLineSet; /** Copy action */ private static CopyAction copyAction = (CopyAction)CopyAction.get (CopyAction.class); /** Private instance of Next jump action */ private static NextOutJumpAction nextAction = (NextOutJumpAction)NextOutJumpAction.get (NextOutJumpAction.class); /** Private instance of Previous jump action */ private static PreviousOutJumpAction previousAction = (PreviousOutJumpAction)PreviousOutJumpAction.get (PreviousOutJumpAction.class); /** output settings */ private static OutputSettings outputSettings = (OutputSettings)OutputSettings.findObject (OutputSettings.class, true); /** Performer for jump actions */ private JumpActionPerformer jumpPerformer = new JumpActionPerformer(); /** Mapping from the line numbers to listeners (Integer, Listener) */ private TreeMap listeners = new TreeMap (); /** Map of lineNO:ide.text.Line * @associates Map*/ private Map int2Line; /** Message format for exception parsing */ private static MessageFormat formatOfException; /** PopupMenu */ JPopupMenu jPopup; private JMenuItem copyItem; private JMenuItem clearItem; /** Creates pane without association to a tab. */ public OutPane() { this (null); } /** Creates new OutPane in the specific OutputTab */ public OutPane(OutputTab tab) { this.tab = tab; int2Line = new WeakHashMap(29); this.model = new PaneWriter (); this.writer = new OutputWriterImpl (model); setModel (model); setCellRenderer (jumpPerformer); setBackground (outputSettings.getBaseBackground ()); setSelectionMode (ListSelectionModel.SINGLE_SELECTION); jumpLineSet = false; addMouseListener(this); addListSelectionListener (this); addKeyListener(this); addFocusListener(this); jPopup = new JPopupMenuPlus(); // add copy copyItem = new JMenuItem(org.openide.util.NbBundle.getBundle (OutPane.class).getString ("CTL_ClipboardCopy")); copyItem.addActionListener(this); jPopup.add(copyItem); jPopup.addSeparator(); // add clear clearItem = new JMenuItem(org.openide.util.NbBundle.getBundle (OutPane.class).getString ("CTL_Clear")); clearItem.addActionListener(this); jPopup.add(clearItem); add(jPopup); } /** The writer for this pane. */ public OutputWriter getOut () { return writer; } private OutputListener getListenerForPosition(int pos) { return (OutputListener)listeners.get (new Integer (pos)); } /** Checks (sets/unsets) performer for CopyAction */ final void checkCopyAction() { ActionPerformer aperf = (getSelectedIndex () != -1 ? this : null); copyAction.setActionPerformer(aperf); } void checkNextPrevActions() { // if (tab.amISelected(this)) { nextAction.setActionPerformer((nextJump() != -1) ? jumpPerformer : null); previousAction.setActionPerformer((previousJump() != -1) ? jumpPerformer : null); checkCopyAction(); // } // Those comments are part of a bugfix - checkNextPrevAction doesn't work // when you are pressing alt-F8/alt-F7 in the editor } private int nextJump() { Integer i = new Integer (getSelectedIndex () + 1); synchronized (listeners) { SortedMap sm = listeners.tailMap (i); if (sm.isEmpty ()) return -1; i = (Integer)sm.firstKey (); } return i.intValue (); } private int previousJump() { Integer i = new Integer (getSelectedIndex () - 1); synchronized (listeners) { SortedMap sm = listeners.headMap (i); if (sm.isEmpty ()) { return -1; } i = (Integer)sm.lastKey (); } return i.intValue (); } /** This method is called whenever jump line is written. It tests if this line * is first and if it is sets the cursor to this position */ private void stopAtFirstJumpLine(int index) { if (!jumpLineSet) { setSelectedIndex (index); jumpLineSet = true; } } /** Clears whole lines table */ void clearLineTable() { synchronized (listeners) { Iterator it = listeners.entrySet ().iterator (); while (it.hasNext ()) { Map.Entry e = (Map.Entry)it.next (); int pos = ((Integer)e.getKey ()).intValue (); OutputListener listener = (OutputListener) e.getValue (); listener.outputLineCleared(new OutputEventImpl(tab, pos)); } listeners.clear (); jumpLineSet = false; checkNextPrevActions(); } } /** My implementation of OutputEvent. It has lazy initialized line text. */ private class OutputEventImpl extends OutputEvent { private int index; static final long serialVersionUID =-437312909583471519L; public OutputEventImpl(InputOutput src, int index) { super(src); } /** Returns text on the line. * @return the text on the line */ public String getLine () { return (String) model.getElementAt(index); } } private static final String EMPTY = " "; // NOI18N /** Writer, which can insert text into the output document. * @see java.io.Writer */ class PaneWriter extends Writer implements ListModel { /** Array with all lines (String) except current line * @associates String*/ ArrayList lines; /** Store current line */ private StringBuffer currentLine; private String currentLineStr; /** the buffer with text to add */ private StringBuffer sb; /** true if a request for redraw has been send */ private boolean sent = false; /** Last printed character was '\r' */ private boolean lastR = false; /** only one listener for the model */ private ListDataListener dataListener; public PaneWriter () { super(); lines = new ArrayList(); currentLine = new StringBuffer(); currentLineStr = EMPTY; sb = new StringBuffer(); } void cleaned () { sent = true; redraw (); } /** Draws text from buffer to editor. * Must be called from synchronized methods. */ private synchronized void redraw() { final int origSize = getSize(); String bufferStr = sb.toString(); StringTokenizer tok = new StringTokenizer(bufferStr, "\n\r\t", true); // NOI18N String tab = null; while (tok.hasMoreTokens()) { String token = tok.nextToken(); if (token.equals("\n") || token.equals("\r")) { // NOI18N boolean r = token.equals("\r"); // NOI18N if (r || !lastR) { String addLine = (currentLine.length() == 0) ? EMPTY : currentLine.toString(); lines.add(addLine); currentLine.setLength(0); currentLineStr = EMPTY; } lastR = r; } else { if (token.equals("\t")) { // NOI18N if (tab == null) tab = getTab(); token = tab; } currentLine.append(token); currentLineStr = null; } } sb.setLength(0); SwingUtilities.invokeLater(new Runnable () { public void run () { if (!sent) return; sent = false; int currentSize = getSize(); if (currentSize != origSize) { fireIntervalAdded(origSize, currentSize); } else { fireContentsChanged(currentSize, currentSize); } int selIndex = getSelectedIndex(); ensureIndexIsVisible((selIndex == -1) ? currentSize - 1: selIndex); } }); } void fireChanges() { } public synchronized void write(char[] cbuf, int off, int len) throws IOException { if (tab != null && tab.isClosed()) { tab.rebindTab(); } sb.append(new String(cbuf, off, len)); // requests redraw sent = true; redraw (); } /** Prints the given string and register OutputListener on this line */ public synchronized void println(String s, OutputListener l) throws IOException { if (tab != null && tab.isClosed()) { tab.rebindTab(); } if (sb.length() > 0) sb.append("\n"); // NOI18N int indx = getSize(); sb.append(s); sb.append("\n"); // NOI18N boolean empty = listeners.isEmpty(); listeners.put(new Integer(indx), l); // flush the new line sent = true; redraw(); if (empty) { // stop at this position setSelectedIndex(indx); // fires info about the change valueChanged (null); } checkNextPrevActions(); } public synchronized void flush() throws IOException { if (tab != null && tab.isClosed()) throw new IOException(); sent = true; redraw(); } public synchronized void close() throws IOException { if (tab != null && tab.isClosed()) throw new IOException(); // to signal that all requests for redraw has been sent sent = false; reset(); } public synchronized void reset() { clearLineTable(); int2Line.clear(); int size = getSize(); lines.clear(); sb.setLength(0); currentLine.setLength(0); currentLineStr = EMPTY; if (size >= 0) { fireIntervalRemoved (0, size); } } // // ListDataListener methods // public Object getElementAt (int i) { if (i < lines.size()) return lines.get(i); else if ((i == lines.size()) && (currentLine.length() > 0)) { if (currentLineStr == null) currentLineStr = currentLine.toString(); return currentLineStr; } else { return EMPTY; } } public int getSize () { return lines.size() + ((currentLine.length() > 0) ? 1 : 0); } /** * Add a listener to the list that's notified each time a change * to the data model occurs. * @param l the ListDataListener */ public void addListDataListener(ListDataListener l) { if (dataListener != null) { // only one listener supported throw new InternalError (); } dataListener = l; } /** * Remove a listener from the list that's notified each time a * change to the data model occurs. * @param l the ListDataListener */ public void removeListDataListener(ListDataListener l) { dataListener = null; } protected void fireContentsChanged(int index0, int index1) { if (dataListener != null) dataListener.contentsChanged( new ListDataEvent(this, ListDataEvent.CONTENTS_CHANGED, index0, index1) ); } protected void fireIntervalAdded(int index0, int index1) { if (dataListener != null) dataListener.intervalAdded ( new ListDataEvent(this, ListDataEvent.INTERVAL_ADDED, index0, index1) ); } protected void fireIntervalRemoved(int index0, int index1) { if (dataListener != null) dataListener.intervalRemoved ( new ListDataEvent(this, ListDataEvent.INTERVAL_REMOVED, index0, index1) ); } } /** Will be returned by TopOutput (Output class implements it), as an instance of OutputWriter. */ class OutputWriterImpl extends OutputWriter { PaneWriter writer; public OutputWriterImpl (PaneWriter writer) { super(writer); this.writer = writer; } public void println (String s, OutputListener l) throws IOException { writer.println(s, l); } public void reset() { writer.reset(); } } private class JumpActionPerformer extends JLabel implements ActionPerformer, ListCellRenderer { { // initializer this.setOpaque (true); } static final long serialVersionUID =4405590143900383138L; /** Performer for actions */ public void performAction(SystemAction action) { int jump = -1; if (action instanceof NextOutJumpAction) { jump = nextJump(); } if (action instanceof PreviousOutJumpAction) { jump = previousJump(); } if (jump != -1) { setSelectedIndex (jump); if (action instanceof NextOutJumpAction) { ensureIndexIsVisible (jump + 2); } ensureIndexIsVisible (jump); invokeJumpListener(jump); } checkNextPrevActions(); } /** Component for rendering the cell. */ public java.awt.Component getListCellRendererComponent (JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { String newVal = (value instanceof String) ? (String) value : ((StringBuffer)value).toString (); setText(newVal); boolean isJump = listeners.containsKey (new Integer (index)) || parseException(index, false); if (index != list.getSelectedIndex()) { setBackground (outputSettings.getBaseBackground ()); setForeground (outputSettings.getBaseForeground ()); } else { if (isJump) { setBackground (outputSettings.getJumpCursorBackground ()); setForeground (outputSettings.getJumpCursorForeground ()); } else { setBackground (outputSettings.getCursorBackground ()); setForeground (outputSettings.getCursorForeground ()); } } setFont(new java.awt.Font("monospaced", java.awt.Font.PLAIN, outputSettings.getFontSize ())); // NOI18N return this; } } /** @return true if there were an listner */ private boolean invokeJumpListener(int index) { OutputListener listener = getListenerForPosition(index); if (listener != null) { listener.outputLineAction(new OutputEventImpl(tab, index)); return true; } else { return false; } } // ListSelectionListener method /** previous selected index */ private int previousIndex = -1; public void valueChanged (ListSelectionEvent ev) { int indx = getSelectedIndex (); if (indx == previousIndex) return; previousIndex = indx; OutputListener listener = getListenerForPosition(indx); if (listener != null) { listener.outputLineSelected(new OutputEventImpl(tab, indx)); } checkNextPrevActions(); } // mouse listener methods public void mouseClicked (MouseEvent evt) { if ((evt.getClickCount() == 2) && ((evt.getModifiers() & MouseEvent.BUTTON1_MASK) != 0)) { int loc = locationToIndex(evt.getPoint()); if (! invokeJumpListener(loc)) { parseException(loc, true); } } } public void mousePressed(MouseEvent e) { if (isPopupTrigger(e)) { showPopup(e); } } /** @return true if this event is popup menu trigger... */ private static boolean isPopupTrigger(MouseEvent evt) { return (evt.getModifiers() & (MouseEvent.BUTTON2_MASK | MouseEvent.BUTTON3_MASK)) != 0; } /** Shows "copy" popup menu */ // NOI18N private void showPopup(MouseEvent ev) { jPopup.show(this, ev.getX(), ev.getY()); } // focus listener public void focusGained(FocusEvent ev) { checkCopyAction(); } public void focusLost(FocusEvent ev) { // do not do copyAction.setActionPerformer(null); // doesn't work with main window } /** Parses exception or tries to find * @param openEditor <tt>true</tt> if an editor should be open * @return <tt>true</tt> if the line is recognized as part of exception dump */ private boolean parseException(int loc, boolean openEditor) { if (loc < 0) { return false; } String s = ((String) model.getElementAt(loc)).trim(); Integer locI = new Integer(loc); try { Object aLine = int2Line.get(locI); Line l; if (aLine == null) { Object[] o = getExceptionFormat().parse(s); String all = o[0].toString(); String file = o[1].toString(); String ext = o[2].toString(); int line = Integer.parseInt(o[3].toString()); int i = all.indexOf(file); String path = all.substring(0, i + file.length()).replace('.', '/') + ".java"; // NOI18N FileObject fo = TopManager.getDefault().getRepository().findResource(path); if (fo == null) { int2Line.put(locI, int2Line); return false; } DataObject data = DataObject.find(fo); EditorCookie cookie = (EditorCookie) data.getCookie(EditorCookie.class); if (cookie == null) { int2Line.put(locI, int2Line); return false; } l = cookie.getLineSet().getOriginal (line - 1); } else if (aLine == int2Line) { // already parsed - not soccess return false; } else { l = (Line) aLine; } if (openEditor) { l.show(Line.SHOW_GOTO, 0); } return true; // ignore all } catch (java.text.ParseException e) { // MsgForm.parse } catch (NumberFormatException e) { // Integer.parseInt } catch (DataObjectNotFoundException e) { // DO.find } catch (IndexOutOfBoundsException e) { // getLine } int2Line.put(locI, int2Line); return false; } /** Getter for a MessageFormat that describes a line of exception dump. */ private static MessageFormat getExceptionFormat() { if (formatOfException == null) { formatOfException = new MessageFormat(OutputSettings.getString("MSG_Exception_Line")); //at {0}({1}.{2}:{3}) } return formatOfException; } /** Performer for copy action */ public void performAction(SystemAction action) { doCopy(); } /** Performer for registerKeyboardAction */ public void actionPerformed(ActionEvent e) { if (e.getSource () == copyItem) doCopy(); else doClear (); } /** Inserts selected indexes into the clipboard */ private void doCopy() { final Object[] o = model.lines.toArray(); StringBuffer buff = new StringBuffer(o.length * 25); if (o.length >= 1) { buff.append(o[0].toString()); for (int i = 1; i < o.length; i++) { buff.append("\n").append(o[i].toString()); // NOI18N } } StringSelection ss = new StringSelection(buff.toString()); TopManager.getDefault().getClipboard ().setContents (ss, ss); } private void doClear() { model.lines.clear (); model.cleaned (); } public void mouseReleased(MouseEvent e) {} public void mouseEntered(MouseEvent e) {} public void mouseExited(MouseEvent e) {} // key listener waits for enter public void keyTyped (KeyEvent e) { } public void keyPressed (KeyEvent e) { if (e.getKeyCode () == KeyEvent.VK_ENTER) { e.consume (); int indx = getSelectedIndex(); if (indx < 0) { return; } if (! invokeJumpListener(indx)) { parseException(indx, true); } } } public void keyReleased (KeyEvent e) { } static String getTab() { int tabSize = outputSettings.getTabSize(); StringBuffer buf = new StringBuffer(tabSize); for (int j = 0; j < tabSize; j++) buf.append(" "); // NOI18N return buf.toString(); } } /* * Log * 45 Gandalf 1.44 3/16/00 Martin Ryzl #5687 * 44 Gandalf 1.43 3/11/00 Martin Ryzl menufix [by E.Adams, * I.Formanek] * 43 Gandalf 1.42 3/9/00 Ales Novak #5687 * 42 Gandalf 1.41 2/7/00 Ales Novak #5613 * 41 Gandalf 1.40 1/18/00 Ales Novak ALT-F7/F8 - open editor * 40 Gandalf 1.39 1/12/00 Ales Novak i18n * 39 Gandalf 1.38 12/30/99 Jaroslav Tulach New dialog for * notification of exceptions. * 38 Gandalf 1.37 12/23/99 Radko Najman bug 4022 * 37 Gandalf 1.36 12/17/99 Ales Novak #4135 * 36 Gandalf 1.35 10/22/99 Ian Formanek NO SEMANTIC CHANGE - Sun * Microsystems Copyright in File Comment * 35 Gandalf 1.34 10/7/99 Ales Novak next error action works * even from the java editor * 34 Gandalf 1.33 10/6/99 Ian Formanek Fixed bug 3183 - Output * Window is not cleared immediatelly after clicking Clear Output. * 33 Gandalf 1.32 10/6/99 Jaroslav Tulach ProgressEvent.TASK_CLEANING * * 32 Gandalf 1.31 9/28/99 Petr Hamernik #3980 fixed * 31 Gandalf 1.30 9/21/99 Petr Hamernik #3841 + caching * rewritten * 30 Gandalf 1.29 8/13/99 Ales Novak a single character on a * line was not printed - bug * 29 Gandalf 1.28 8/9/99 Ian Formanek Generated Serial Version * UID * 28 Gandalf 1.27 8/3/99 Ales Novak slow redrawing fixed * 27 Gandalf 1.26 8/2/99 Petr Hamernik fixed bug #2599 * 26 Gandalf 1.25 7/30/99 Ales Novak race condition * 25 Gandalf 1.24 7/30/99 Jaroslav Tulach getOriginal & getCurrent * in LineSet * 24 Gandalf 1.23 7/28/99 Ales Novak bugfix #2826 * 23 Gandalf 1.22 7/20/99 Ian Formanek Popup menu on output * window * 22 Gandalf 1.21 7/15/99 Petr Hamernik optimization * 21 Gandalf 1.20 6/22/99 Ales Novak Copying changed * 20 Gandalf 1.19 6/8/99 Ian Formanek ---- Package Change To * org.openide ---- * 19 Gandalf 1.18 5/20/99 Ales Novak exception parsing + copy * action * 18 Gandalf 1.17 5/6/99 Ales Novak newlines fix * 17 Gandalf 1.16 5/5/99 Ales Novak scrollbars fixed * 16 Gandalf 1.15 4/22/99 Ales Novak fixed "red" lines from * compiler * 15 Gandalf 1.14 4/9/99 Ales Novak fix for newlines * 14 Gandalf 1.13 4/8/99 Ales Novak * 13 Gandalf 1.12 3/29/99 Jaroslav Tulach Displayes tabs. * 12 Gandalf 1.11 3/29/99 Ales Novak * 11 Gandalf 1.10 3/21/99 Jaroslav Tulach Keys. * 10 Gandalf 1.9 3/19/99 Jaroslav Tulach * 9 Gandalf 1.8 3/19/99 Jaroslav Tulach * 8 Gandalf 1.7 3/19/99 Jaroslav Tulach * 7 Gandalf 1.6 3/18/99 Jaroslav Tulach * 6 Gandalf 1.5 3/18/99 Jaroslav Tulach println opens the tab * 5 Gandalf 1.4 3/17/99 Jaroslav Tulach Output Window fixing. * 4 Gandalf 1.3 3/11/99 Ales Novak removed comments * 3 Gandalf 1.2 2/27/99 Jaroslav Tulach Shortcut changed to * Keymap * 2 Gandalf 1.1 2/18/99 Ales Novak * 1 Gandalf 1.0 1/5/99 Ian Formanek * $ * Beta Change History: * 0 Tuborg 0.17 --/--/98 Jan Jancura Tab switching changed * 0 Tuborg 0.18 --/--/98 Jaroslav Tulach new syntax behaviour * 0 Tuborg 0.19 --/--/98 Petr Hamernik bugfix * 0 Tuborg 0.20 --/--/98 Petr Hamernik bugfix */